/********************************************************************
        File :			Channel.cpp
        Creation date :	2010/6/27
		
        License :			Copyright 2010 Ahmed Charfeddine, http://www.pushframework.com

                                   Licensed under the Apache License, Version 2.0 (the "License");
                                   you may not use this file except in compliance with the License.
                                   You may obtain a copy of the License at
				
                                           http://www.apache.org/licenses/LICENSE-2.0
				
                                   Unless required by applicable law or agreed to in writing, software
                                   distributed under the License is distributed on an "AS IS" BASIS,
                                   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                   See the License for the specific language governing permissions and
                                   limitations under the License.
	
	
 *********************************************************************/
#include "StdAfx.h"
#include "Channel.h"

#include "IOCPQueue.h"
#include "ServerImpl.h"

#include "Dispatcher.h"
#include "ServerStats.h"

#include "ScopedLock.h"

#include "../include/ListenerOptions.h"


namespace PushFramework {

    CChannel::CChannel(ServerImpl* pServerImpl, ListenerOptions* pListenerOption) {
        this->pServerImpl = pServerImpl;
        this->pListenerOption = pListenerOption;

        this->bReusable = bReusable;

        status = Free;

        oBuffer.Allocate(pListenerOption->uIntermediateSendBufferSize);
        inBuffer.Allocate(pListenerOption->uIntermediateReceiveBufferSize);

    

        ::InitializeCriticalSection(&csLock);
    }

    CChannel::~CChannel(void) {



        ::DeleteCriticalSection(&csLock);
    }

    int CChannel::getStatus() {
        return status;
    }

    CChannel::Key& CChannel::getKey() {
        return key;
    }

    int& CChannel::getRefCount() {
        return refCount;
    }

    double CChannel::getLifeDuration() {
        time_t current = time(NULL);
        return difftime(current,dtCreationTime);
    }

 

    bool CChannel::PostReceive() {


        return true;
    }

    int CChannel::ReadReceivedBytes(int& dwIoSize) {

        dwIoSize = 0;
        ScopedLock lock(csLock);

        int nMaxToPick = min(pListenerOption->uReadBufferSize,inBuffer.getRemainingSize());

         int ret;
        while (true) {
            ret = recv(socket, inBuffer.GetBuffer()+inBuffer.GetDataSize(), nMaxToPick, 0);
            if (ret != EAGAIN)
                break;
        }


         if(ret < 0){
             //?
         }
         else if(ret ==0){
             return status;
         }
         else{
             dwIoSize = ret;
             inBuffer.GrowSize(ret);
             return status;
         }

    }

    bool CChannel::PushPacket(OutgoingPacket* pPacket) {
        ScopedLock lock(csLock);
        if (status < Connected) {
            return true;
        }

        unsigned int uWrittenBytes = 0;

        Protocol* pProtocol = pListenerOption->pProtocol;

        int iResult = pProtocol->serializeOutgoingPacket(*pPacket, oBuffer, uWrittenBytes);
        //int iResult = pProtocol->serializeOutgoingPacket(pPacket, oBuffer.GetBuffer()+ oBuffer.GetDataSize(), oBuffer.getRemainingSize(),uWrittenBytes);

        if (iResult != Protocol::Success) {
            pServerImpl->getStats()->addToCumul(ServerStats::BandwidthRejection, uWrittenBytes);
            return false;
        }

        pServerImpl->getStats()->addToCumul(ServerStats::BandwidthOutstanding, uWrittenBytes);
        std::string serviceName;
        if (pServerImpl->getDispatcher()->getCurrentService(serviceName)) {
            pServerImpl->getStats()->addToDistribution(ServerStats::BandwidthOutboundVolPerRequest, serviceName, uWrittenBytes);
        }

        //oBuffer.GrowSize(uWrittenBytes);

        if (!bWriteInProgress) {
            return WriteBytes();
        }
        return true;
    }

    bool CChannel::WriteBytes() {
        //Try send some data  :
        int dwToPick = min(pListenerOption->uSendBufferSize, oBuffer.GetDataSize());

        if (dwToPick == 0)
            return true;

        int ret;
        while (true) {
            ret = send(socket, oBuffer.GetBuffer(), dwToPick, 0);
            if (ret != EAGAIN)
                break;
        }

        if (ret < 0) {

            if (ret == EINTR) {
                //Interrupted
                bWriteInProgress = TRUE;
            }
            perror("Error on send");

        } else if (ret == 0) {
            //Closed connection
        } else {
            oBuffer.Pop(ret);
            bool reportSentData = !isObserverChannel();

            if (reportSentData) {
                pServerImpl->getStats()->addToCumul(ServerStats::BandwidthOutbound, ret);
                pServerImpl->getStats()->addToKeyedDuration(ServerStats::BandwidthOutboundPerConnection, getKey(), ret);
            }
        }


        return true;
    }

    int CChannel::OnSend(bool& bIsBufferIdle) {
        ScopedLock lock(csLock);

        bWriteInProgress = FALSE;


        WriteBytes();
        
        bIsBufferIdle = oBuffer.GetDataSize() == 0;
        return status;
    }

    void CChannel::Close(bool bWaitForSendsToComplete) {
        ScopedLock lock(csLock);

        if (status < Connected) {
            return;
        }

        //Either connected or attached :
        if (bWaitForSendsToComplete) {
            if (oBuffer.GetDataSize() == 0) {
                CloseSocket();
            } else
                status = WaitingForWrite;
        } else {
            CloseSocket();
        }
    }

    void CChannel::CloseSocket() {
        close(socket);
        status = Released;
    }

    std::string CChannel::getClient() {
        return clientKey;
    }

    void CChannel::saveContext(void* lpContext) {
        this->lpContext = lpContext;
    }

    void* CChannel::getContext() {
        return lpContext;
    }

    DataBuffer& CChannel::GetReceiveBuffer() {
        return inBuffer;
    }

    void CChannel::attachToClient(const char* clientKey) {
        this->clientKey = clientKey;
        status = Attached;
    }

    bool CChannel::isObserverChannel() {
        return bIsObserver;
    }

    unsigned int CChannel::getPeerPort() {
        return rPeerPort;
    }

    std::string CChannel::getPeerIP() {
        return rPeerIP;
    }

    void CChannel::initialize(SOCKET s, SOCKADDR_IN address, bool bIsObserver) {
        socket = s;

        rPeerPort = address.sin_port;
        rPeerIP = inet_ntoa(address.sin_addr);

        status = Connected;


        static Key dwKeyCount = 1;
        key = dwKeyCount++;

        refCount = 0;

        dtCreationTime = time(NULL);


        bWriteInProgress = FALSE;


        this->bIsObserver = bIsObserver;
    }

    void CChannel::unintialize() {
        status = Free;
    }

    bool CChannel::isReusable() {
        return bReusable;
    }

    Protocol* CChannel::getProtocol() {
        return pListenerOption->pProtocol;
    }

}
